﻿#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//枚举的使用
//enum xuanzhe
//{
//	EXIT,//0 默认值第一个是0，枚举中每一个成员的叫枚举常量，会递增
//	ADD,//1
//	SUB,//2
//	MUL,//3
//	DIV//4
//};
// 
//void meun()
//{
//	printf("************************************\n");
//	printf("**********1、ADD      2、SUB********\n");
//	printf("**********3、MUL      4、DIV********\n");
//	printf("*************  0、EXIT   ***********\n");
//	printf("************************************\n");
//}
//int main()
//{
//	int input;
//	do
//	{
//		meun();
//		printf("请选择：");
//		scanf("%d", &input);
//		switch (input)
//		{
//			case ADD:   //使用枚举的用处可增加其可维护性和可读性
//				break;  //case不用1、2、3、4排列 用枚举可以不用按顺序  ADD就是1 就是加法 SUB就是2 就是减法 输入是多少就执行哪一个
//			case SUB:
//				break;
//			case MUL:
//				break;
//			case DIV:
//				break;
//			case EXIT:
//				printf("退出\n");
//				break;
//			default:
//				printf("选择错误，重新选\n");
//				break;
//		}
//	} while (input);
//	return 0;
//}

//联合体的关键字：union
//结构体的关键字：struct
//枚举的关键字：enum
/***************************************************************
联合体
 联合体类型的声明
像结构体⼀样，联合体也是由⼀个或者多个成员构成，这些成员可以不同的类型。
但是编译器只为最⼤的成员分配⾜够的内存空间。联合体的特点是所有成员共⽤同⼀块内存空间。所
以联合体也叫：共⽤体。
给联合体其中⼀个成员赋值，其他成员的值也跟着变化。
同一时间只能用一个 成员。
****************************************************************/
//struct S
//{
//	char c;//偏移量是0 从0开始 1 8 1
//	//1~3 浪费了
//	int i;//4 8 4  占4~7
//};
//
//union Un
//{
//	char c;
//	int i;
//};
//
//int main()
//{
//	printf("%zd\n", sizeof(struct S));//8
/*********************************************************
联合体的特点
联合的成员是共⽤同⼀块内存空间的，这样⼀个联合变量的⼤⼩，⾄少是最⼤成员的⼤⼩（因为联合
⾄少得有能⼒保存最⼤的那个成员）。
********************************************************** /
//	printf("%zd\n", sizeof(union Un));//只为最⼤的成员分配⾜够的内存空间 公用一块空间4
//
//	return 0;
//}



/*********************************************************
联合体⼤⼩的计算
• 联合的⼤⼩⾄少是最⼤成员的⼤⼩。
• 当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候，就要对⻬到最⼤对⻬数的整数倍。
**********************************************************/
//union Un
//{
//	char c;//同一时间只能用一个 c或i。
//	int i;
//};
//
//int main()
//{
//	union Un un = { 0 };
//	un.i = 0x11223344;
//	un.c = 0x55;
//	//printf("%zd\n", sizeof(un));
//	//printf("%p\n", &un);//公用一块空间
//	//printf("%p\n", &(un.c));
//	//printf("%p\n", &(un.i));
//
//	return 0;
//}
//

//
//union Un
//{
//	char arr[5];//5   1 8 1
//	int i;//4         4 8 4
//};


//union Un
//{
// 联合体公用一块空间 对比结构体可以节省空间
//	short arr[7];//14    2 8 2  short是两个字节 7个元素7个short就是14个字节
//	int i;//4                4  联合体的大小至少是最大成员数的大小 那就是至少是14个字节
//								当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候，就要对⻬到最⼤对⻬数的整数倍。
//								14不是最大对齐数 4 的整数呗 所以要对齐到最大对齐数整数倍 16   所以这个联合体大小是16个字节
//};
//
//int main()
//{
//	printf("%d\n", sizeof(union Un));//16
//
//	return 0;
//}
//

/****************************************************************************
* 使⽤联合体是可以节省空间的，举例：
⽐如，我们要搞⼀个活动，要上线⼀个礼品兑换单，礼品兑换单中有三种商品：图书、杯⼦、衬衫。
每⼀种商品都有：库存量、价格、商品类型和商品类型相关的其他信息。
图书：书名、作者、⻚数
杯⼦：设计
衬衫：设计、可选颜⾊、可选尺⼨
****************************************************************************/
//struct gift_list
//{
//	//公共属性
//	int stock_number;//库存量
//	double price; //定价
//	int item_type;//商品类型
//	//特殊属性
//	char title[20];//书名
//	char author[20];//作者
//	int num_pages;//⻚数
//
//	char design[30];//设计
//	int colors;//颜⾊
//	int sizes;//尺⼨
//};
/****************************************************************************
上述的结构其实设计的很简单，⽤起来也⽅便，但是结构的设计中包含了所有礼品的各种属性，这样
使得结构体的⼤⼩就会偏⼤，⽐较浪费内存。因为对于礼品兑换单中的商品来说，只有部分属性信息
是常⽤的。⽐如：
商品是图书，就不需要design、colors、sizes。
所以我们就可以把公共属性单独写出来，剩余属于各种商品本⾝的属性使⽤联合体起来，这样就可以
介绍所需的内存空间，⼀定程度上节省了内存。
****************************************************************************/
struct gift_list
{
	int stock_number;//库存量
	double price; //定价
	int item_type;//商品类型

	union {
		struct
		{
			char title[20];//书名
			char author[20];//作者
			int num_pages;//⻚数
		}book;
		struct //只用一次 结构体可以匿名 
		{
			char design[30];//设计
		}mug;
		struct
		{
			char design[30];//设计
			int colors;//颜⾊
			int sizes;//尺⼨
		}shirt;
	}item;
};
//写一个程序，判断当前机器是大端？还是小端？
//
int main()
{
	int a = 1;
 //a 0x00 00 00 01 字节表示
 //    高字节    低字节
 //内存是从低地址到高存储
	if (*(char*)&a == 1)//取第一个字节的值 是1就是小端存储
	{
		printf("小端\n");//低字节放低地址
	}
	else//第一个字节的值是0 就是大端
	{
		printf("大端\n");//高字节放高地址
	}
	//0x 00 00 00 01
	//
	return 0;
}

//union Un
//{
//	char c; //1 公用一块空间
//	int i; //4
//};
//
//int main()
//{
//	union Un un = { 0 };
//	un.i = 1;
// 因为c和i公用一块空间
// i = 1 =  0x00 00 00 01
// c在i的第一个位置 如果是内存是小端存储的话 c里面就是01  所以就是小端存储 
//	if (un.c == 1)
//	{
//		printf("小端\n");
//	}
//	else
//	{
//		printf("大端\n");
//	}
//	//0x 00 00 00 01
//	//
//	return 0;
//}
/******************************************************************
*  malloc和free
* malloc和free都声明在 stdlib.h 头⽂件中
2.1 malloc  
C语⾔提供了⼀个动态内存开辟的函数：
1 void* malloc (size_t size);
这个函数向内存申请⼀块连续可⽤的空间，并返回指向这块空间的指针。
• 如果开辟成功，则返回⼀个指向开辟好空间的指针。
• 如果开辟失败，则返回⼀个 NULL 指针，因此malloc的返回值⼀定要做检查。
• 返回值的类型是 void* ，所以malloc函数并不知道开辟空间的类型，具体在使⽤的时候使⽤者⾃⼰来决定。
• 如果参数 size 为0，malloc的⾏为是标准是未定义的，取决于编译器
********************************************************************/
//int main()
//{
//	int n = 0;  //常见的申请内存
//	int arr[10] = { 0 };
//
//	return 0;
//}
//
//
//int main()
//{
//	//申请10个整型的空间
//	//开辟什么类型的空间 就强转为 什么类型 因为函数返回值是void*
//	int* p = (int*)malloc(10*sizeof(int));开辟10个int类型的空间 就是40个字节
//	if (p == NULL) 加个保障 防止开辟的是个野指针
//	{
//		//如果空间开辟失败 就直接返回
//		perror("malloc");//perror函数用来将上一个函数发生错误的原因输出
//		return 1;
//	}
//	//可以使用这个40个字节的
//	//int i = 0;
//	//for (i = 0; i < 10; i++)
//	//{
//	//	*(p + i) = i + 1;
//	//}
//	//释放  开辟动态内存空间一定要释放 

//	free(p);
//	p = NULL; //将p置空指针 防止变成野指针
//	//
//	return 0;
//}


//int main()
//{
//	
//	//
//	int* p = (int*)malloc(INT_MAX);//INT_MAX是整型最大值的关键字 开辟一个最大值
//	if (p == NULL)
//	{
//		//会空间开辟失败  值太大了无法开辟
//		perror("malloc");
//		return 1;
//	}
//	//可以使用这个40个字节的
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		*(p + i) = i + 1;
//	}
//
//
//	return 0;
//}

/******************************************************************
*free函数专⻔是⽤来做动态内存的释放和回收的
free函数⽤来释放动态开辟的内存。
• 如果参数 ptr 指向的空间不是动态开辟的，那free函数的⾏为是未定义的。
• 如果参数 ptr 是NULL指针，则函数什么事都不做
********************************************************************/
//int main()
//{
//	int arr[10] = { 0 };
//	int* p = arr;
//
//	//...
//释放内存  开辟动态内存空间一定要释放 
//	free(p);//虽然已经释放了 但是还是能找到p的空间 此时p就会变成野指针
//	p = NULL;//将p置空指针 防止变成野指针 都是与动态空间开辟后释放内存使用
//
//	return 0;
//}


//int main()
//{
//	///....
//	int* p = NULL;
//	//...
//	free(p);//如果参数 p 是NULL指针，则函数什么事都不做
//
//	return 0;
//}

/******************************************************************
* calloc
C语⾔还提供了⼀个函数叫 calloc ， calloc 函数也⽤来动态内存分配。原型如下：
1 void* calloc(size_t num, size_t size);
• 函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间，并且把空间的每个字节初始化为0。
• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
********************************************************************/
//int main()
//{
//	//申请10个整型的空间
//	//int*p = (int*)malloc(10*sizeof(int));//这么写是不想算开辟10个整型空间有多少个字节  malloc不会初始化
//	int* p = (int*)calloc(10, sizeof(int));//calloc函数可以直接给要开辟多少个、什么类型大小的 空间 calloc比malloc多一个参数  在返回地址之前把申请的空间的每个字节初始化为全0
//	if (p == NULL)
//	{
//		perror("calloc");
//		return 1;
//	}
//	//使用空间
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", p[i]);//*(p+i)
//	}
//	//释放
//	free(p);
//	p = NULL;
//
//	return 0;
//}
//int main()
//{
//	//申请10个整型的空间
//	int* p = (int*)calloc(10, sizeof(int));
//	if (p == NULL)
//	{
//		perror("calloc");
//		return 1;
//	}
//	//使用空间
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", p[i]);//*(p+i)
//	}
//
//	//调整空间-希望变成20个整型空间
//	int* ptr = (int*)realloc(p, 12 * sizeof(int));//?
//	if (ptr != NULL)
//	{
//		p = ptr;
//	}
//	//使用
//	//...
//	
//	//释放
//	free(p);
//	p = NULL;
//
//	return 0;
//}
//
/*******************************************************************
* realloc
• realloc函数的出现让动态内存管理更加灵活。
• 有时会我们发现过去申请的空间太⼩了，有时候我们⼜会觉得申请的空间过⼤了，那为了合理的使
⽤内存，我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤⼩的调整。
函数原型如下：
1 void* realloc (void* ptr, size_t size);
• ptr 是要调整的内存地址
• size 调整之后新⼤⼩
• 返回值为调整之后的内存起始位置。
• 这个函数调整原内存空间⼤⼩的基础上，还会将原来内存中的数据移动到 新 的空间。
********************************************************************/
//int main()
//{
//	//申请10个整型的空间
//	int* p = (int*)calloc(10, sizeof(int));
//	if (p == NULL)
//	{
//		perror("calloc");
//		return 1;
//	}
//	//使用空间
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", p[i]);//*(p+i)
//	}
//
//	//调整空间-希望变成20个整型空间
// //两种情况 空间够的话就在p后面直接开辟空间够
//            空间不够的话就直接开辟新的空间来存p
// 最好创建个新的指针*ptr 这样开辟不超过的话不会影响到p
//	int* ptr = (int*)realloc(p, 20 * sizeof(int));//调整p的地址为20个整型的大小 
//	if (ptr != NULL)
//	{
//		p = ptr;
//	}
//	//使用
//	//...
//	
//	//释放
//	free(p);
//	p = NULL;
//
//	return 0;
//}

/*******************************************************************
* 开辟动态内存常见错误
********************************************************************/
//对NULL指针的解引⽤操作
//int main()
//{
//	int* p  = (int*)malloc(10*sizeof(int));
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//直接使用的话 没有上面的判断p是否为空指针的代码 p可能是个空指针 野指针
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		p[i] = i;//*(p+i)
//	}
//
//	free(p);
//	p = NULL;
//
//	return 0;
//}

//对动态开辟空间的越界访问
//int main()
//{
//	int* p = (int*)malloc(10 * sizeof(int));
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//使用
//	int i = 0;
//	for (i = 0; i < 40; i++)//开辟10个整型空间不能用40个
//	{
//		p[i] = i;//*(p+i)空指针加0还是空指针解引用
//	}
//
//	free(p);
//	p = NULL;
//
//	return 0;
//}
//

//对⾮动态开辟内存使⽤free释放
//int main()
//{
//	int a = 10;
//	int*p = &a;
//	//...
//
//	free(p);//free函数只能对动态开辟内存使用
//	p = NULL;
//	
//	return 0;
//}
//


//使⽤free释放⼀块动态开辟内存的⼀部分
//int main()
//{
//	int* p = (int*)malloc(10 * sizeof(int));
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//使用
//	int i = 0;
//	for (i = 0; i <5; i++)
//	{
//		*p = i;
//		p++;//p不再指向动态内存的起始位置
//	}
//
//	free(p);p不再指向动态内存的起始位置 不能释放其中的一部分 要全部释放
//	p = NULL;
//
//	return 0;
//}
// 
//对同⼀块动态内存多次释放
//int main()
//{
//	int* p = (int*)malloc(10 * sizeof(int));
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//使用
//	free(p);
//	p = NULL;
//  逻辑上不允许 已经置为空指针了 下面再置于空指针相当于给野指针置空指针
//	//...
//	free(p);
//	p = NULL;
//
//	return 0;
//}

//动态开辟内存忘记释放（内存泄漏）
//void test()
//{
//	int flag = 1;
//	int*p = (int*)malloc(100);
//	if (p == NULL)
//	{
//		//
//		return;
//	}
//
//	//使用
//
//	if (flag)//某一个条件发生了 直接返回了 下面的函数没法执行 一直不释放 忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。
//		return;
//
//	free(p);
//	p = NULL;
//}
//
//int main()
//{
//	test();
//	
//	//.....
//
//	return 0;
//}

//int main()
//{
//	//int* p = malloc(20);
//	//realloc(p, 40);
//
//	int*p = (int*)realloc(NULL, 40);//给realloc传个空指针它没地址调整 就直接开辟空间== malloc(40)  realloc函数不经能调整也可以开辟空间的
//	if (p == NULL)
//	{
//
//	}
//	return 0;
//}
/*******************************************************************
* 动态内存经典笔试题分析
********************************************************************/
//第一题  传值传址调用问题
//void GetMemory(char* p)//p接收的是str是个空指针
//{
//    p = (char*)malloc(100);
//	//申请了100个字节的空间 地址赋给了 p p可以找到这开辟的空间地址 str是找不到
//}
//
//void Test(void)
//{
//    char* str = NULL;
//    GetMemory(str);//str是空指针传上去 是传值调用 不是传地址
//  //执行完GetMemory之后str依然是个空指针 把字符串拷贝到str里面 str是个空指针 没有有效空间 对NULL解引用操作形成了对空指针的非法访问 程序会崩溃
//	//执行完malloc没有释放内存空间 会造成内存数据泄露
//    strcpy(str, "hello world");
//    printf(str);
//    //printf("%s\n", str);
//}
//
//int main()
//{
//    //char* p = "hehe\n";
//	  //printf(str);//printf函数接收到一个字符串地址就能打印
//    //printf("hehe\n");
//    //printf(p);
//
//    Test();
//    return 0;
//}
//#include <string.h>
////第一种解决方法
//void GetMemory(char** p) //str已经是个char*一级指针 传参就要变成二级指针来应用 就可以找到str了 传参不会影响实参
//{
//    *p = (char*)malloc(100);
//    //此时开辟的动态内存空间地址赋给了*p 通过二级指针解引用就能找到str的地址 str现在就是100个字节
//}
//
//void Test(void)
//{
//    char* str = NULL;
//    GetMemory(&str);//换成传址调用
//    strcpy(str, "hello world");//ok
//    printf(str);//ok
//    free(str);//再加上释放内存空间 防止内存数据泄露
//    str = NULL;
//}
//
//int main()
//{
//    Test();
//    return 0;
//}


#include <string.h>
//第二种解决类型
//char* GetMemory()
//{   //什么类型的指针就返回什么类型
//    char* p = (char*)malloc(100);
//    return p;//直接将开辟的空间地址返回回去
//}
//
//void Test(void)
//{
//    char* str = NULL;//什么类型的数据就接收什么类型
//    str = GetMemory();
//	  //str就接收到了开辟的空间地址 进行拷贝操作就会没有
//    strcpy(str, "hello world");//ok
//    printf(str);//ok
//    free(str);
//    str = NULL;
//}
//
//int main()
//{
//    Test();
//    return 0;
//}

//第二题  //返回栈空间地址的问题局部变量是放在栈区的  可以返回变量 不可以返回它的地址
//char* GetMemory(void)
//{   //因为字符串是局部的 出了这个函数 就找不到这个字符串了没有使用权限 所以打印不出
//    char p[] = "hello world";
//    return p;
//}
//
//void Test(void)
//{
//    char* str = NULL;
//    str = GetMemory();//虽然已经接收到了一个指针地址 但是没有使用权限 所以它就是个野指针
//    printf(str);
//}
//
//int main()
//{
//    Test();
//    return 0;
//}

